package com.xuxueli.applyModules.utils;
import com.aspose.cells.*;
import com.xuxueli.applyModules.entity.ExportConfig;
import com.xuxueli.applyModules.entity.ExportDataSource;
import com.xuxueli.applyModules.entity.HashMapDataTable;
import com.xuxueli.applyModules.dto.DcBillHeadDto;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 导出通用助手类
 */
@Slf4j
@Component
public class ExportUtil {

    @Value("${dc.export.template}")
    private String templatePath;
    @Value("${dc.export.temp}")
    public String tempPath;

    /**
     * Aspose方式导出（需要模板）
     *
     * @param data         aspose导出数据源
     * @param tempFileName Aspose模板文件路径
     * @return 文件的字节数组
     * @throws Exception
     */
    public static byte[] export(ExportDataSource data, String tempFileName) throws Exception {
        return export(data, tempFileName, false);
    }

    /**
     * Aspose方式印打（需要模板）
     *
     * @param data         aspose导出数据源
     * @param tempFileName Aspose模板文件路径
     * @return 文件的字节数组
     * @throws Exception
     */
    public static byte[] print(ExportDataSource data, String tempFileName) throws Exception {
        return export(data, tempFileName, true);
    }

    /**
     * Aspose方式导出（需要模板）
     *
     * @param data         aspose导出数据源
     * @param tempFileName Aspose模板文件路径
     * @return 文件的字节数组
     * @throws Exception
     */
    private static byte[] export(ExportDataSource data, String tempFileName, Boolean isExportPdf) throws Exception {
        if (!getLicense()) {
            throw new Exception("Can't get aspose license!");
        }
        if (data == null) {
            throw new Exception("导出Aspose数据源为空!");
        }
        if (StringUtils.isBlank(tempFileName)) {
            throw new Exception("导出模板文件为空!");
        }

        File file = new File(tempFileName);
        if (!file.exists()) {
            throw new Exception("导出模板文件不存在!");
        }

        return getFileStream(data, tempFileName, isExportPdf);
    }

    /**
     * 通用简单方式导出（不需要模板）,导出格式为xls
     *
     * @param data       导出实体集合数据源
     * @param configList 导出列配置集合，如果为空则导出实体所有属性
     * @param <T>        实体类型
     * @return
     */
    public static <T> byte[] export(List<T> data, List<ExportConfig> configList) throws Exception {
        return export(data, configList, "xls");
    }

    /**
     * 通用简单方式导出（不需要模板）
     *
     * @param data       导出实体集合数据源
     * @param configList 导出列配置集合，如果为空则导出实体所有属性
     * @param extName 导出文件扩展名(xls,xlsx,xlsm,pdf)
     * @param <T>        实体类型
     * @return
     */
    public static <T> byte[] export(List<T> data, List<ExportConfig> configList, String extName) throws Exception {
//        if (!getLicense()) {
//            throw new Exception("Can't get aspose license!");
//        }
//        if (data == null || data.size() == 0) {
//            throw new Exception("导出数据集合为空!");
//        }
//        return getFileStream(data, configList,extName);
        return export(data,configList,extName,"Sheet1");
    }

    /**
     * 通用简单方式导出（不需要模板）
     *
     * @param data       导出实体集合数据源
     * @param configList 导出列配置集合，如果为空则导出实体所有属性
     * @param extName 导出文件扩展名(xls,xlsx,xlsm,pdf)
     * @param sheetName sheet名称
     * @param <T>        实体类型
     * @return
     */
    public static <T> byte[] export(List<T> data, List<ExportConfig> configList,String extName,String sheetName) throws Exception {
        if (!getLicense()) {
            throw new Exception("Can't get aspose license!");
        }
        if (data == null || data.size() == 0) {
            throw new Exception("导出数据集合为空!");
        }
        if(Strings.isBlank(sheetName)){
            sheetName = "Sheet1";
        }
        return getFileStream(data, configList,extName,sheetName);
    }

    /**
     * 核注清单打印
     * @param head
     * @param list
     * @param blist
     * @param fileName
     * @param tname
     * @param <H>
     * @param <T>
     * @param <V>
     */
    public <H,T,V> String exportExcel(List<H> head, List<T> list, List<V> blist, String fileName,String tname,String... footer) {
        // 验证License
        if (!getLicense()) {
            return "";
        }
        try {
            //临时文件路径
            String tempfile = tempPath + fileName;
            //模板路径
            String fp = templatePath + tname;


            if (head.size() > 0) {
                Workbook wb = new Workbook(fp);
                WorkbookDesigner designer = new WorkbookDesigner();

                if(footer.length > 0) {
                    //设置页脚的参数
                    setFooter(wb, footer);
                }
                designer.setWorkbook(wb);
                designer.setDataSource("Head",head);
                designer.setDataSource("List",list);
                designer.setDataSource("Bill",blist);
                WorksheetCollection wc = wb.getWorksheets();
                if ("iBillTemplate.xlsx".equalsIgnoreCase(tname) || "eBillTemplate.xlsx".equalsIgnoreCase(tname)) {
                    //插入条形码图片
                    for (int i = 0; i < head.size(); i++) {
                        DcBillHeadDto dcBillHead = (DcBillHeadDto) head.get(i);
                        if (dcBillHead.getListNo()!=null){
                            BarcodeUtil.createBarcode(dcBillHead.getListNo(),tempPath+dcBillHead.getListNo()+".png");
                            wc.get(i).getPictures().add(1, 10,tempPath+dcBillHead.getListNo()+".png");
                        }
                    }
                }
                designer.process();
                wb.save(tempfile);
                wb.dispose();

            }
            return tempfile;

        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    /**
     * 设置页脚的参数
     * @param wb
     * @param footer
     */
    private void setFooter(Workbook wb, String[] footer) {

        //获取sheet的个数
        int zz = wb.getWorksheets().getCount();

        //设置企业内部编号的值
        String ret = "";
        for (int i = 0; i < footer.length; i++) {
            String footerRet = footer[i];
            if(footerRet.contains("&")){
                footerRet = footerRet.replace("&","&&");
            }
            //ret += footer[i];
            ret += footerRet;
        }
        //将企业内部编号放入页脚
        for (int z = 0; z < zz; z++){
            String f1 = wb.getWorksheets().get(z).getPageSetup().getFooter(1);
            wb.getWorksheets().get(z).getPageSetup().setFooter(1, String.format(f1+"  %s",ret));
        }
    }


    // region Aspose方式导出具体实现

    /**
     * 获取文件流
     *
     * @param data         aspose导出数据源
     * @param tempFileName Aspose模板文件路径
     * @return
     * @throws Exception
     */
    private static byte[] getFileStream(ExportDataSource data, String tempFileName, Boolean isExportPdf) throws Exception {
        Workbook wb = null;
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            wb = new Workbook(tempFileName);
            WorkbookDesigner designer = new WorkbookDesigner();
            designer.setWorkbook(wb);
            designer.setCalculateFormula(true);
            if (data.getVriableMap() != null && !data.getVriableMap().isEmpty()) {
                for (Map.Entry<String, Object> entry : data.getVriableMap().entrySet()) {
                    designer.setDataSource(entry.getKey(), entry.getValue());
                }
            }
            if (data.getTableMap() != null && !data.getTableMap().isEmpty()) {
                for (Map.Entry<String, Object> entry : data.getTableMap().entrySet()) {
                    designer.setDataSource(entry.getKey(), entry.getValue());
                }
            }
            if (data.getBodyHashMap() != null && !data.getBodyHashMap().isEmpty()) {
                for (Map.Entry<String, Object> entry : data.getBodyHashMap().entrySet()) {
                    designer.setDataSource(entry.getKey(), new HashMapDataTable((List<Map<String, Object>>)entry.getValue()));
                }
            }

            designer.process();
            int saveFormat = getSaveFormat(tempFileName, isExportPdf);
            wb.save(os,saveFormat);
            os.close();
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            throw ex;
        } finally {
            if (wb != null) {
                wb.dispose();
            }
        }
        return os.toByteArray();
    }

    /**
     * 根据文件名获取保存选项
     *
     * @param tempFileName
     * @return
     */
    private static int getSaveFormat(String tempFileName, Boolean isExportPdf) {
        if (isExportPdf) {
            return SaveFormat.PDF;
        }
        String extName = tempFileName.substring(tempFileName.lastIndexOf('.') + 1);
        switch (extName) {
            case "xls":
                return SaveFormat.EXCEL_97_TO_2003;
            case "xlsx":
                return SaveFormat.XLSX;
            case "xlsm":
                return SaveFormat.XLSM;
        }
        return SaveFormat.EXCEL_97_TO_2003;
    }

    private static int getSaveFormat(String extName){
        switch (extName.toLowerCase()) {
            case "xls":
                return SaveFormat.EXCEL_97_TO_2003;
            case "xlsx":
                return SaveFormat.XLSX;
            case "xlsm":
                return SaveFormat.XLSM;
            case "pdf":
                return SaveFormat.PDF;
            default:
                return SaveFormat.EXCEL_97_TO_2003;
        }
    }

    // endregion

    // region 通用简易方式导出具体实现

    /**
     * 获取文件流
     *
     * @param data
     * @param configList
     * @param <T>
     * @return
     * @throws Exception
     */
    private static <T> byte[] getFileStream(List<T> data, List<ExportConfig> configList,String extName,String sheetName) throws Exception {
        Workbook wb = null;
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            wb = new Workbook();
            wb.getWorksheets().add();
            Worksheet sheet = wb.getWorksheets().get(0);
            sheet.setName(sheetName);
            Cells cells = sheet.getCells();
            T t = data.get(0);
            Class<?> clazz = t.getClass();
            Map<Field, Method> fieldsAllMap = loadColumnField(clazz);
            Style headStyle = getHeadCellStyle(wb);
            if (configList == null || configList.size() == 0) {
                // 循环输出表头
                int j = 0;
                for (Field field : fieldsAllMap.keySet()) {
                    Cell cell = cells.get(0, j);
                    cell.setValue(field.getName());
                    cell.setStyle(headStyle);
                    j++;
                }
                setCellsValue(cells, data, fieldsAllMap);
            } else {
                // 循环输出表头
                int j = 0;
                for (ExportConfig config : configList) {
                    Cell cell = cells.get(0, j);
                    cell.setValue(config.getFiledNameCn());
                    cell.setStyle(headStyle);
                    j++;
                }

                Map<Field, Method> fieldsMap = convertField(fieldsAllMap, configList);
                setCellsValue(cells, data, fieldsMap);
            }

            int saveFormat = getSaveFormat(extName);
            wb.save(os,saveFormat);

            os.close();
        } catch (Exception ex) {
            log.error(ex.getMessage(), ex);
            throw ex;
        } finally {

            if (wb != null) {
                wb.dispose();
            }
        }
        return os.toByteArray();
    }

    private static Style getHeadCellStyle(Workbook workbook){
        Style headStyle = workbook.createStyle();
        headStyle.getFont().setBold(true);  //文字加粗
//        headStyle.setName("宋体");  //文字字体
//        headStyle.getFont().setSize(13);  //文字大小
        headStyle.setHorizontalAlignment(TextAlignmentType.CENTER);

        return headStyle;
    }

    /**
     * 转换配置信息
     *
     * @param mapAll
     * @param configList
     * @return
     */
    private static Map<Field, Method> convertField(Map<Field, Method> mapAll, List<ExportConfig> configList) {
        Map<Field, Method> map = new LinkedHashMap<Field, Method>();
        for (ExportConfig config : configList) {
            for (Field field : mapAll.keySet()) {
                if (config.getFiledName().equals(field.getName())) {
                    map.put(field, mapAll.get(field));
                    break;
                }
            }
        }
        return map;
    }

    /**
     * 对Cells赋值
     *
     * @param cells
     * @param data
     * @param fieldsMap
     * @param <T>
     * @throws Exception
     */
    private static <T> void setCellsValue(Cells cells, List<T> data, Map<Field, Method> fieldsMap) throws Exception {
        int j = 0;
        String decimalFormat = "###############.#########";
        String intFormat = "###############";

        for (int i = 0; i < data.size(); i++) {
            j = 0;
            for (Field field : fieldsMap.keySet()) {
                Object value = getFieldValue(data.get(i), fieldsMap.get(field), field);
                if(value != null && value instanceof BigDecimal) {
                    String strCellValue = ((BigDecimal) value).toPlainString();
                    if(!strCellValue.startsWith("0") && !strCellValue.startsWith("-0") ) {
                        Style style = cells.get(i + 1, j).getStyle();
                        style.setCustom(decimalFormat);
                        if (strCellValue.indexOf(".") == -1) {
                            style.setCustom(intFormat);
                        }
                        cells.get(i + 1, j).setStyle(style);
                    }
                }

                cells.get(i + 1,j).setValue(value);
                j++;
            }
        }
    }

    /**
     * 获取Field的值
     *
     * @param obj
     * @param method
     * @param <T>
     * @return
     * @throws Exception
     */
    private static <T> Object getFieldValue(Object obj, Method method, Field field) throws Exception {
        Object value = method.invoke(obj);
        if (value == null) {
            return value;
        }

        if (value instanceof Date) {
            Date date = (Date) value;

            String pattern = "yyyy-MM-dd HH:mm:ss";
            if (field != null) {
                DateTimeFormat[] dateTimeFormats = field.getAnnotationsByType(DateTimeFormat.class);
                if (dateTimeFormats != null && dateTimeFormats.length > 0) {
                    pattern = dateTimeFormats[0].pattern();
                }
            }
            return DateUtils.dateToString(date, pattern);
        } else if (value instanceof LocalDate) {
            LocalDate date = (LocalDate) value;
            return date.format(DateTimeFormatter.ISO_LOCAL_DATE);
        } else if (value instanceof LocalDateTime) {
            LocalDateTime dateTime = (LocalDateTime) value;
            return dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        } else if (value instanceof BigDecimal) {
            BigDecimal n = (BigDecimal)value;
            if (n.unscaledValue().intValue() == 0) {
                return BigDecimal.ZERO;
            }
            if (n.scale() <= 0) {
                return n;
            }
            BigDecimal stripped = n.stripTrailingZeros();
            if (stripped.scale() < 0) {
                stripped = n.setScale(0);
            }
            return stripped;
        }

        return value;
    }

    /**
     * 加载列信息
     *
     * @param clazz
     * @param <T>
     * @return
     * @throws Exception
     */
    private static <T> Map<Field, Method> loadColumnField(Class<T> clazz) throws Exception {
        Map<Field, Method> fieldMap = new LinkedHashMap<Field, Method>();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (!"serialVersionUID".equals(field.getName())) {
                Method m = clazz.getMethod("get" + getMethodName(field.getName()));
                fieldMap.put(field, m);
            }
        }
        return fieldMap;
    }

    /**
     * 获取方法名
     *
     * @param fildeName
     * @return
     */
    private static String getMethodName(String fildeName) {
        byte[] items = fildeName.getBytes();
        if((char) items[0] >= 'A' && (char) items[0] <='Z'){
            return new String(items);
        }
        items[0] = (byte) ((char) items[0] - 'a' + 'A');
        return new String(items);
    }

    // endregion

    /**
     * 获取license
     *
     * @return
     */
    private static boolean getLicense() {
        boolean result = false;
        try {
            InputStream is = ExportUtil.class.getClassLoader().getResourceAsStream("/files/license.xml");
            License aposeLic = new License();
            aposeLic.setLicense(is);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}
